Repository navigation
0.8.0 — a state holds only itself, and a step can give it up - #29
Merged
Merged
Conversation
step(x, state, donate=True) says the caller will not read the state again, so a sub-layer may write its successor into the buffers it was given. The default keeps the successor guarantee for everyone who does not ask. It is a keyword on the one method rather than a fourth method, because the path that needs it is a trunk stepping many streams: it has to pass through Block.step and Stack.step, and every component keeps the same three methods. Gated DeltaNet and Undertow accept it and ignore it -- their successors cost no more than their arithmetic. A block passes it on only when it is set, so a stateful sub-layer from outside the library that predates the keyword keeps stepping, and fails loudly only when it is asked to donate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
By default step returns a new table per stream, O(B·N·d_v) a step against O(B·(W+R)·d_v) of arithmetic. It read as a large-table cost and is as much a batch cost: at B = 1 the step is launch-bound and flat in N, which hides it. Measured on one machine at d_model 512, a step at B = 128 went from 4.1 ms at 32² slots to 37.6 ms at 128²; donated, it is 1.9 to 2.3 ms at every size. In a five-block trunk with two such layers the copy was 61% of a step at 64² and 86% at 128², at B = 128. recurrent_sparse_delta takes in_place, so both paths are one body with scatter or scatter_ as the last write and agree bit for bit. Donation is a permission: the layer still copies a table that is a view -- including init_state's broadcast, which under "learned" shares the parameter's storage and at B = 1 would let a write through into the learned table -- and a table autograd is recording or a torch.func transform is batching. The first donated step from init_state is therefore a copy, and the rest are in place. Tests hold each of those, and that the in-place write actually happens. The module docstring, the package's, n_slots's and the user page said per-token work does not depend on N, unqualified. They now say the arithmetic does not and the successor copy does. The record's §3.6 has the measurements and the reasons for a keyword over a step_ method or a paged table; its open item on an in-place decode is closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Released with #28 and this branch together. #28 makes a state returned by forward hold its own bytes and no more -- three mixers were returning views that kept a larger buffer alive. This branch adds step(x, state, donate=True) to every mixer, Block and Stack, and Sparse Delta Memory uses it to write the table in place rather than copying it per stream per step. 0.8.0 rather than a patch for a keyword every step now takes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Released as 0.8.0 together with #28. The version bump is the last commit here. Merge #28 first, then this; the two combine without conflicts, and the combined tree passes 650 tests.
Closes #26.
By default,
SparseDeltaMemory.stepreturns a new table per stream:O(B·N·d_v)a step, againstO(B·(W+R)·d_v)of arithmetic. At a large batch that copy is most of a decode step, even at ordinary table sizes. This PR adds a way to waive it:donate=Truesays the caller will not readstateagain, so a sub-layer may write its successor into the buffers it was given. The default is unchanged: every caller who doesn't opt in keeps the successor guarantee.Shape
step_method. The path that needs it is a trunk stepping many streams, so the option has to pass throughBlock.stepandStack.step. A keyword does that, and every component keeps the same three methods.Blockpasses the keyword on only when it is set. A stateful sub-layer from outside the library that doesn't take it keeps stepping, and fails loudly only if someone asks it to donate.Donating is a permission, not a demand
recurrent_sparse_deltatakesin_place. Both paths run one body whose last write is eitherscatterorscatter_, so they agree bit for bit. The layer still copies when the table is not the stream's own to write:init_state's broadcast. Under"learned"that broadcast shares the parameter's storage, and atB = 1it does not overlap, so an in-place write would go through and change the learned table.torch.functransform.So the first donated step after
init_stateis a copy, and every step after it writes in place.Docs
N, with no qualification: the module docstring, then_slotsdocstring, the package docstring anddocs/SDM.md. They now say the arithmetic does not, and the successor copy does.docs/SDM.mdsaid "at small tables it does not matter", which is false atB = 128. Its Generation section anddocs/BLOCK.mdnow documentdonate.Measured
One machine (Tesla P40, fp32), through the API:
step(x, state)againststep(x, state, donate=True). Median of 3 runs of 64 steps each, after 8 warm-up steps. Outputs were bit-equal in every row.One layer,
d_model=512,n_heads=2,W=R=64,initial_memory="learned":Stack.stepwith 5 blocks,d_model=512,d_mlp=1024, SDM at blocks 1 and 3 and Gated DeltaNet elsewhere (each stack value is stable across 5 repeats):Tests
initial_memoryoptions atB = 1andB = 3;init_state, under bothno_gradandinference_mode;vmap.donatereaches every stateful sub-layer only when set, and a sub-layer without the keyword still steps.Each guard was checked by removing it: dropping the view check, dropping the transform check, ignoring
donate, or passing it unconditionally each fails the test meant to catch it.🤖 Generated with Claude Code